package org.jmc.refmanager.domain.dao.jpa;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

import org.jmc.refmanager.domain.dao.OrgUnitDao;
import org.jmc.refmanager.domain.model.OrgUnit;
import org.jmc.refmanager.util.ListUtil;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.stereotype.Repository;

@Repository("orgUnitDao")
public class OrgUnitDaoImpl extends GenericDaoJpa<OrgUnit, Integer> implements
		OrgUnitDao {

	public OrgUnitDaoImpl() {
		super(OrgUnit.class);
	}

	/**
	 * 
	 * @param service
	 * @param newParent
	 */
	private void moveHierarchyToChild(OrgUnit service, OrgUnit newParent) {
		// Deplacement de la hierarchie fille vers l'ancien pere du service
		String fromNodeId = service.getPath();
		String toNodeId = service.getParentPath();
		
		String updateDirectChildren = "UPDATE OrgUnit SET parentPath=REPLACE(parentPath, ?1, ?2), " 
			+ " deplace=true, depth=LENGTH(?2)-LENGTH(REPLACE(?2, '.', '')), parentId=SUBSTRING_INDEX(?2, '.', -1)"
			+ " WHERE parentPath = ?1 AND supprime=false";
		final Query updateDirectChildrenQuery = this.createQuery(updateDirectChildren);
		
		// Noeud de depart
		updateDirectChildrenQuery.setParameter(1, fromNodeId);
		// Noeud d'arrivee
		updateDirectChildrenQuery.setParameter(2, toNodeId);
		
		execute(new JpaCallback<Void>() {
			@Override
			public Void doInJpa(EntityManager em) throws PersistenceException {
				updateDirectChildrenQuery.executeUpdate();
				return null;
			}
		});

		String updateDeepChildren = "UPDATE OrgUnit SET parentPath=REPLACE(parentPath, ?1, ?2)," 
			+ " deplace=true, depth=LENGTH(?2)-LENGTH(REPLACE(?2, '.', ''))"
			+ " WHERE parentPath LIKE ?3 AND supprime=false";
		final Query updateDeepQuery = this.createQuery(updateDeepChildren);
		
		updateDeepQuery.setParameter(1, fromNodeId);
		// Noeud de depart
		updateDeepQuery.setParameter(2, toNodeId);
		// les descendants de tous les fils
		updateDeepQuery.setParameter(3, fromNodeId + ".%");
		
		execute(new JpaCallback<Void>() {
			@Override
			public Void doInJpa(EntityManager em) throws PersistenceException {
				updateDeepQuery.executeUpdate();
				return null;
			}
		});

		// TODO Mise e jour du noeud lui-meme ?
	}
	
	@Override
	public void moveHierarchy(OrgUnit service, Integer newParentId) {
		// Sinon, rechercher le pere (pour voir s'il n'a pas bouge)
		OrgUnit newParent = this.find(newParentId);

		String fromParentPath = service.getParentPath();
		String toParentPath = service.getParent().getPath();
		
		String fromNodeId = fromParentPath + "." + String.format("%05d", service.getId());
		String toNodeId = toParentPath + "." + String.format("%05d", service.getId());
		
		if (newParent.getPath().startsWith(service.getPath())) {
//		if (newParent.getParentPath().startsWith(service.getPath()) ||
//				newParent.getParentPath().startsWith(service.getParentPath())) {
			// Deplacement dans un de ses fils
			moveHierarchyToChild(service, newParent);
			
			String newParentPath = newParent.getPath().replace(String.format("%05d", service.getId()), "");
			newParentPath = newParentPath.replace("..", ".");
			service.setParentPath(newParentPath);
		} else {
			// Deplacement dans une autre branche
			moveSimpleHierarchy(fromNodeId, toNodeId);
			
			service.setParentPath(newParent.getPath());
//			int depth = service.getParentPath().replaceAll(OrgUnit.HIERARCHY_DEPTH, "").length();
//			service.setDepth(depth);
		}

		int depth = service.getParentPath().replaceAll(OrgUnit.HIERARCHY_DEPTH, "").length();
		service.setDepth(depth);
		// Appliquer les chanagements de racines au noeud courant
		service.setParent(newParent); // => positionne le parentId
		// Positionnement du flag de deplacement
		service.setDeplace(true);
	}

	/**
	 * @param fromNodeId
	 * @param toNodeId
	 */
	private void moveSimpleHierarchy(String fromNodeId, String toNodeId) {
		// La requete est la substitution du parentPath mais pas de la totalite.
		// Si un noeud a comme chaine "0.1.2.3.4.5" et que la racine "0.1.2" change pour
		// "12.13.14" alors le noeud est "12.13.14.3.4.5"
		String updateDeepChildren = "UPDATE OrgUnit SET parentPath=REPLACE(parentPath, ?2, ?1)," 
			+ " deplace=true, depth=LENGTH(?1)-LENGTH(REPLACE(?1, '.', ''))"
			+ " WHERE parentPath LIKE ?3 AND supprime=false";
		final Query updateDeepQuery = this.createQuery(updateDeepChildren);
		
		updateDeepQuery.setParameter(1, toNodeId);
		// Noeud de depart
		updateDeepQuery.setParameter(2, fromNodeId);
		// les descendants de tous les fils
		updateDeepQuery.setParameter(3, fromNodeId + ".%");
		
		execute(new JpaCallback<Void>() {
			@Override
			public Void doInJpa(EntityManager em) throws PersistenceException {
				updateDeepQuery.executeUpdate();
				return null;
			}
		});
		
		// Mise e jour des noeuds directs
		String updateDirectChildren = "UPDATE OrgUnit SET parentPath=REPLACE(parentPath, ?2, ?1)," 
			+ " deplace=true, depth=LENGTH(?1)-LENGTH(REPLACE(?1, '.', ''))"
			+ " WHERE parentPath=?2 AND supprime=false";
		final Query updateDirectQuery = this.createQuery(updateDirectChildren);
		
		updateDirectQuery.setParameter(1, toNodeId);
		// Noeud de depart
		updateDirectQuery.setParameter(2, fromNodeId);
		
		execute(new JpaCallback<Void>() {
			@Override
			public Void doInJpa(EntityManager em) throws PersistenceException {
				updateDirectQuery.executeUpdate();
				return null;
			}
		});
	}

	@Override
	public void deleteHierarchy(OrgUnit rootService) {
		// Selectionner tous les fils ayant le meme chemin parent
		String rootPath = rootService.getPath();
		String motifSuppression = rootService.getMotifSuppression();

		final Query jpaQuery = this.createNamedQuery(OrgUnit.UPDATE_SET_DELETED);
		
		jpaQuery.setParameter(1, motifSuppression);
		// Les fils directs
		jpaQuery.setParameter(2, rootPath);
		// les descendants de tous les fils
		jpaQuery.setParameter(3, rootPath + ".%");
		// Le noeud racine lui-meme
		jpaQuery.setParameter(4, rootService.getId());
		
		execute(new JpaCallback<Void>() {
			@Override
			public Void doInJpa(EntityManager em) throws PersistenceException {
				jpaQuery.executeUpdate();
				return null;
			}
		});
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<OrgUnit> listDirections() {
		return super.findByNamedQuery(OrgUnit.FIND_TYPE_C02);
	}

	@SuppressWarnings("unchecked")
	@Override
	public OrgUnit getDirectionValidation(OrgUnit service) {
		Map<String, Integer> params = new HashMap<String, Integer>();
		params.put("id", service.getId());
		List<OrgUnit> directions = super.findByNamedQueryAndNamedParams(OrgUnit.FIND_DIRECTION_VALIDATION, params);
		return ListUtil.getFirstEvenIfNull(directions);
	}

	// Pessimistic locking
	// Session.load(), specifying a LockMode. 
	// LockMode.UPGRADE can be acquired upon explicit 
	// user request using SELECT ... FOR UPDATE on databases
	// which support that syntax. 
}
